package xin.sparrow.log.handler;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import xin.sparrow.log.annotation.WebLog;
import xin.sparrow.log.enums.HttpCodeEnum;
import xin.sparrow.log.handler.bean.ExceptionResponseData;
import xin.sparrow.log.handler.bean.NormalResponseData;
import xin.sparrow.log.request.RequestAndResponseLog;
import xin.sparrow.log.request.RequestThreadLocal;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author  wancheng on 2019/3/20.
 */
@Slf4j
@RestController
@RestControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice {

    private final String description_success = "%s : success";
    private final String description_error = "%s : error";

    /**
     * 对象参数bind 异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(BindException.class)
    public ExceptionResponseData exceptionHandler(HttpServletRequest request, BindException ex) {
        String url = request.getRequestURI();
        StringBuilder errorMesssage = new StringBuilder("校验失败:");
        errorMesssage.append(ex.getFieldErrors()
                .stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(",")));
        return new ExceptionResponseData(HttpCodeEnum.HTTP_BIND_ERROR_CODE, url, errorMesssage.toString());
    }

    /**
     * 普通参数bind异常
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ExceptionResponseData exception2Handler(HttpServletRequest request, ConstraintViolationException ex) {
        String url = request.getRequestURI();
        StringBuilder errorMesssage = new StringBuilder("校验失败:");
        errorMesssage.append(ex.getMessage());
        return new ExceptionResponseData(HttpCodeEnum.HTTP_BIND_ERROR_CODE, url, errorMesssage.toString());
    }
    /**
     * 全局异常捕捉处理
     *
     * @param request
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ExceptionResponseData globalControllerException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        RequestAndResponseLog requestAndResponseLog = RequestThreadLocal.get();
        long endTime = System.currentTimeMillis();
        long startTime = requestAndResponseLog.getTime().getTime();
        requestAndResponseLog.setResponseTime(endTime - startTime);
        log.error("说明:{} 访问uri:{} 参数:{} 地址:{} 返回结果:{},响应时间:{},ex:{}",
                requestAndResponseLog.getDesc(),
                requestAndResponseLog.getUri(),
                requestAndResponseLog.getReqParam(),
                requestAndResponseLog.getIp(),
                requestAndResponseLog.getResponse(),
                requestAndResponseLog.getResponseTime(),
                ex);
        String message = String.format("%s Fail:",requestAndResponseLog.getDesc());
        return new ExceptionResponseData(HttpCodeEnum.HTTP_EXCEPTION_ERROR_CODE, requestAndResponseLog.getUri(), message + ex.getMessage());
    }
    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        if ("serveFile".equals(methodParameter.getMethod().getName())){
            return false;
        }
        return true;
    }

    @Override
    public Object beforeBodyWrite(@Nullable Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        ServletServerHttpRequest request = (ServletServerHttpRequest) serverHttpRequest;
        WebLog r = methodParameter.getExecutable().getAnnotation(WebLog.class);
        String methodDescription = null;
        if (r != null){
             methodDescription = r.name();
        }
        RequestAndResponseLog requestAndResponseLog = RequestThreadLocal.get();
        requestAndResponseLog.setResponse(JSON.toJSONString(o));
        if(o instanceof ExceptionResponseData){
            ExceptionResponseData exceptionResponseData = (ExceptionResponseData)o;
            return new ExceptionResponseData(exceptionResponseData.getCodeMsg(),exceptionResponseData.getUrl(),exceptionResponseData.getMessage());
        }else if (o instanceof ResponseEntity){
            return o;
        }
        else {
            return new NormalResponseData(HttpCodeEnum.HTTP_SUCCESS,String.format(description_success,methodDescription),o);
        }
    }


}
